package com.forsir.android.mhfinance.helper;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;

import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.widget.Toast;

import com.forsir.android.mhfinance.R;
import com.forsir.android.mhfinance.storeObjects.Account;
import com.forsir.android.mhfinance.storeObjects.Category;
import com.forsir.android.mhfinance.storeObjects.IStorable;
import com.forsir.android.mhfinance.storeObjects.Payment;
import com.forsir.android.mhfinance.storeObjects.Repeating;

public class FileFunctions {
	// TODO something causes Database leak
	// now its static, but it can be changed later
	private static String backupFileName = "mhFinance.bkp";
	private static String backupFileDirectory = "/sdcard/MHFinance";
	private final static int FILE_VERSION = 2;

	private final static int TYPE_STRING = 1;
	private final static int TYPE_LONG = 2;
	private final static int TYPE_INT = 3;
	private final static int TYPE_FLOAT = 4;
	private final static int TYPE_BOOLEAN = 5;

	// must be written out, to prevent change in classes
	private final static String[] CategoryColumns = new String[] { DbAdapter.ID, DbAdapter.CATEGORY_PARENT, DbAdapter.CATEGORY_NAME };
	private final static int[] CategoryTypes = new int[] { TYPE_LONG, TYPE_LONG, TYPE_STRING };
	private final static String[] AccountColumns = new String[] { DbAdapter.ID, DbAdapter.ACCOUNT_NAME };
	private final static int[] AccountTypes = new int[] { TYPE_LONG, TYPE_STRING };
	private final static String[] RepetingColumns = new String[] { DbAdapter.ID, DbAdapter.REPEATING_TYPE, DbAdapter.REPEATING_ACTUAL_DATE, DbAdapter.REPEATING_END_DATE };
	private final static int[] RepetingTypes = new int[] { TYPE_LONG, TYPE_INT, TYPE_LONG, TYPE_LONG };
	private final static String[] PaymentColumns = new String[] { DbAdapter.ID, DbAdapter.PAYMENT_ACCOUNT, DbAdapter.PAYMENT_TRANSFER_ACCOUNT,
	        DbAdapter.PAYMENT_CATEGORY, DbAdapter.PAYMENT_DEPOSIT, DbAdapter.PAYMENT_WITHDRAW, DbAdapter.PAYMENT_REMAINDER, DbAdapter.PAYMENT_ACTUALIZED,
	        DbAdapter.PAYMENT_TYPE, DbAdapter.PAYMENT_DATE, DbAdapter.PAYMENT_MONTH, DbAdapter.PAYMENT_DESCRIPTION, DbAdapter.PAYMENT_REPEATING };
	private final static int[] PaymentTypes = new int[] { TYPE_LONG, TYPE_LONG, TYPE_LONG, TYPE_LONG, TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT, TYPE_BOOLEAN, TYPE_INT,
	        TYPE_LONG, TYPE_INT, TYPE_STRING, TYPE_LONG };

	// Old types
	private final static String[] PaymentColumns1 = new String[] { DbAdapter.ID, DbAdapter.PAYMENT_ACCOUNT, DbAdapter.PAYMENT_TRANSFER_ACCOUNT,
	        DbAdapter.PAYMENT_CATEGORY, DbAdapter.PAYMENT_DEPOSIT, DbAdapter.PAYMENT_WITHDRAW, DbAdapter.PAYMENT_REMAINDER, DbAdapter.PAYMENT_ACTUALIZED,
	        DbAdapter.PAYMENT_TYPE, DbAdapter.PAYMENT_DATE, DbAdapter.PAYMENT_MONTH, DbAdapter.PAYMENT_TIME, DbAdapter.PAYMENT_DESCRIPTION, DbAdapter.PAYMENT_REPEATING };
	private final static int[] PaymentTypes1 = new int[] { TYPE_LONG, TYPE_LONG, TYPE_LONG, TYPE_LONG, TYPE_FLOAT, TYPE_FLOAT, TYPE_FLOAT, TYPE_BOOLEAN, TYPE_INT,
	        TYPE_LONG, TYPE_INT, TYPE_LONG, TYPE_STRING, TYPE_LONG };

	private final DbAdapter mDbAdapter;
	private final Context mContext;

	public FileFunctions(DbAdapter dbAdapter, Context context) {
		mDbAdapter = dbAdapter;
		mContext = context;
	}

	public void Backup() {
		// TODO add time stamp to file
		FileOutputStream file;
		DataOutputStream stream;

		final File folder = new File(backupFileDirectory);
		if (!folder.exists()) {
			if (!folder.mkdir()) {
				Toast.makeText(mContext, mContext.getResources().getString(R.string.error_creating_folder), Toast.LENGTH_LONG).show();
				return;
			}
		}

		try {
			file = new FileOutputStream(backupFileDirectory + "/" + backupFileName);
			stream = new DataOutputStream(file);
		} catch (final FileNotFoundException e) {
			// file not found
			Toast.makeText(mContext, mContext.getResources().getString(R.string.error_opening_file), Toast.LENGTH_LONG).show();
			return;
		}

		try {
			stream.writeInt(FILE_VERSION); // file version
			stream.writeInt(mDbAdapter.getVersion()); // database version

			// Accounts
			SaveCursor(stream, new Account(), AccountColumns, AccountTypes);

			// Categories
			SaveCursor(stream, new Category(), CategoryColumns, CategoryTypes);

			// Repeating
			SaveCursor(stream, new Repeating(), RepetingColumns, RepetingTypes);

			// Payments
			SaveCursor(stream, new Payment(), PaymentColumns, PaymentTypes);

			stream.close();
		} catch (final IOException e) {
			// write error
			Toast.makeText(mContext, mContext.getResources().getString(R.string.error_reading_file), Toast.LENGTH_LONG).show();
		}
		Toast.makeText(mContext, mContext.getResources().getString(R.string.backup_completed), Toast.LENGTH_LONG).show();
	}

	private void SaveCursor(DataOutputStream stream, IStorable entry, String[] columns, int[] types) throws IOException {
		final int number = mDbAdapter.getCount(entry);
		stream.writeInt(number);

		if (number == 0) {
			return;
		}

		final Cursor cursor = mDbAdapter.fetchAllEntries(entry.getTableName(), columns);
		cursor.moveToFirst();
		final int count = cursor.getColumnCount();
		do {
			for (int counter = 0; counter < count; counter++) {
				if (cursor.isNull(counter)) {
					stream.writeByte(0); // write, if value is null
				} else {
					stream.writeByte(1); // write, if value is not null
					switch (types[counter]) {
						case TYPE_STRING:
							stream.writeUTF(cursor.getString(counter));
							break;
						case TYPE_LONG:
							stream.writeLong(cursor.getLong(counter));
							break;
						case TYPE_INT:
							stream.writeInt(cursor.getInt(counter));
							break;
						case TYPE_FLOAT:
							stream.writeFloat(cursor.getFloat(counter));
							break;
						case TYPE_BOOLEAN:
							stream.writeInt(cursor.getInt(counter));
							break;
					}
				}
			}
		} while (cursor.moveToNext());
		cursor.close();
	}

	public void Restore() {
		// TODO add progress dialog
		// TODO add select file
		final File folder = new File(backupFileDirectory);
		if (!folder.exists()) {
			if (!folder.mkdir()) {
				Toast.makeText(mContext, mContext.getResources().getString(R.string.error_creating_folder), Toast.LENGTH_LONG).show();
				return;
			}
		}
		FileInputStream file;
		DataInputStream stream;
		try {
			file = new FileInputStream(backupFileDirectory + "/" + backupFileName);
			stream = new DataInputStream(file);
		} catch (final FileNotFoundException e) {
			Toast.makeText(mContext, mContext.getResources().getString(R.string.error_opening_file), Toast.LENGTH_LONG).show();
			return;
		}

		try {
			final int fileVersion = stream.readInt(); // file version
			stream.readInt(); // database version

			mDbAdapter.beginTrasaction();
			// Accounts
			mDbAdapter.deleteEntries(new Account(), "");
			LoadCursor(stream, new Account(), AccountColumns, AccountTypes);

			// Categories
			mDbAdapter.deleteEntries(new Category(), "");
			LoadCursor(stream, new Category(), CategoryColumns, CategoryTypes);

			// Repeating
			mDbAdapter.deleteEntries(new Repeating(), "");
			LoadCursor(stream, new Repeating(), RepetingColumns, RepetingTypes);

			// Payments
			mDbAdapter.deleteEntries(new Payment(), "");
			if (fileVersion == 1) {
				LoadCursor(stream, new Payment(), PaymentColumns1, PaymentTypes1);
			} else {
				LoadCursor(stream, new Payment(), PaymentColumns, PaymentTypes);
			}

			stream.close();
			mDbAdapter.commitTrasaction();
			Toast.makeText(mContext, mContext.getResources().getString(R.string.restore_cmpleted), Toast.LENGTH_LONG).show();
		} catch (final IOException e) {
			Toast.makeText(mContext, mContext.getResources().getString(R.string.error_reading_file), Toast.LENGTH_LONG).show();
		} finally {
			mDbAdapter.endTrasaction();
		}

	}

	private void LoadCursor(DataInputStream stream, IStorable entry, String[] columns, int[] types) throws IOException {
		final int count = stream.readInt();
		if (count == 0) {
			return;
		}

		final int columnsCount = columns.length;
		ContentValues content = new ContentValues();

		for (int counter = 0; counter < count; counter++) {
			content = new ContentValues();
			for (int column = 0; column < columnsCount; column++) {
				if (stream.readByte() != 0) {
					switch (types[column]) {
						case TYPE_STRING:
							content.put(columns[column], stream.readUTF());
							break;
						case TYPE_LONG:
							content.put(columns[column], stream.readLong());
							break;
						case TYPE_INT:
							content.put(columns[column], stream.readInt());
							break;
						case TYPE_FLOAT:
							content.put(columns[column], stream.readFloat());
							break;
						case TYPE_BOOLEAN:
							content.put(columns[column], stream.readInt());
							break;
					}
				}
			}
			// it is only way, which I found
			content.remove(DbAdapter.PAYMENT_TIME);
			mDbAdapter.createEntry(entry.getTableName(), content);
		}
	}

	public void ExportPayments() {
		// TODO add progress dialog
		// TODO add write file name
		final File folder = new File(backupFileDirectory);
		if (!folder.exists()) {
			if (!folder.mkdir()) {
				Toast.makeText(mContext, mContext.getResources().getString(R.string.error_creating_folder), Toast.LENGTH_LONG).show();
				return;
			}
		}

		final File file;
		FileWriter writer;
		try {
			// TODO change to csv
			file = new File(backupFileDirectory + "/export.txt");
			writer = new FileWriter(file);
		} catch (final IOException e1) {
			Toast.makeText(mContext, mContext.getResources().getString(R.string.error_opening_file), Toast.LENGTH_LONG).show();
			return;
		}

		try {
			SaveCursorPayment(writer);
			writer.close();
		} catch (final IOException e) {
			Toast.makeText(mContext, mContext.getResources().getString(R.string.error_write_file), Toast.LENGTH_LONG).show();
		}
		Toast.makeText(mContext, mContext.getResources().getString(R.string.export_completed), Toast.LENGTH_LONG).show();
	}

	private void SaveCursorPayment(FileWriter writer) throws IOException {
		final Resources resources = mContext.getResources();
		final Payment payment = new Payment();
		final Cursor cursor = mDbAdapter.fetchAllEntries(payment, DbAdapter.PAYMENT_DATE + ", " + DbAdapter.ID);
		cursor.moveToFirst();
		do {
			payment.fillFromCursor(cursor);
			writer.write(payment.getAccount().getId() + ";");
			writer.write(payment.getAccount().getName() + ";");
			if (payment.getType() == Payment.TYPE_TRANSFER_FROM || payment.getType() == Payment.TYPE_TRANSFER_TO) {
				writer.write(payment.getTransferAccount().getName() + ";");
			} else {
				writer.write(payment.getSubcategory().getFullName() + ";");
			}
			writer.write(payment.getDateString(resources) + " " + payment.getTimeString(resources) + ";");
			writer.write(String.valueOf(payment.getTypeString(resources)) + ";");
			writer.write(payment.getAmountString() + ";");
			writer.write(String.valueOf(payment.getRepeating().getTypeString(resources)) + ";");
			writer.write(payment.getDescription());
			writer.write("\n");
		} while (cursor.moveToNext());
		cursor.close();
	}
}
